/*
  Losu Copyright Notice
  --------------------
    Losu is an open source programming language project under the MIT license
  that can be used for both academic and commercial purposes. There are no
  fees, no royalties, and no GNU-like restrictions. Losu qualifies
  as open source software. However, Losu is not public property, and founder
  'chen-chaochen' retains its copyright.

    Losu has been registered with the National Copyright Administration of the
  People's Republic of China, and adopts the MIT license as the copyright
  licensing contract under which the right holder conditionally licenses its
  reproduction, distribution, and modification rights to an unspecified public.

    If you use Losu, please follow the public MIT agreement or choose to enter
  into a dedicated license agreement with us.

  The MIT LICENSE is as follows
  --------------------
  Copyright  2020  chen-chaochen

    Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files (the “Software”), to
  deal in the Software without restriction, including without limitation the
  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  sell copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
*/

#ifndef TAPE_C
#define TAPE_C
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE

#include "tape.h"

#include <dirent.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <direct.h>
#define mkdir(path, mode) _mkdir(path)
#else
#include <unistd.h>
#endif

static bool __tape_readfile(const char* fname, char** data, size_t* len);
static bool __tape_directory(const char* path);

// 从fp读取指定长度的字节到ftmp
static bool read_bytes_to_file(FILE* source_fp,
                               FILE* dest_fp,
                               size_t byte_count) {
  const size_t BUFFER_SIZE = 4096;
  char* buffer = (char*)malloc(BUFFER_SIZE);
  if (!buffer) {
    return true;  // 内存分配失败
  }

  size_t remaining = byte_count;
  while (remaining > 0) {
    size_t to_read = (remaining < BUFFER_SIZE) ? remaining : BUFFER_SIZE;
    size_t bytes_read = fread(buffer, 1, to_read, source_fp);

    if (bytes_read == 0) {
      // 读取失败或到达文件末尾
      free(buffer);
      return true;
    }

    size_t bytes_written = fwrite(buffer, 1, bytes_read, dest_fp);
    if (bytes_written != bytes_read) {
      // 写入失败
      free(buffer);
      return true;
    }

    remaining -= bytes_read;
  }

  free(buffer);
  return false;  // 成功
}

static bool __tape_readfile(const char* fname, char** data, size_t* len) {
  FILE* fp = fopen(fname, "rb");
  if (!fp)
    return 1;
  fseek(fp, 0, SEEK_END);
  *len = ftell(fp);
  fseek(fp, 0, SEEK_SET);
  *data = malloc(*len);
  if (!*data) {
    fclose(fp);
    return 1;
  }
  fread(*data, sizeof(char), *len, fp);
  fclose(fp);
  return 0;
}
// path as '/xxx/xxx'
static bool __tape_directory(const char* path) {
  // char tmp[PATH_MAX] ;
  char* p = NULL;
  size_t len = strlen(path);
  char tmp[len + 1];
  snprintf(tmp, sizeof(tmp), "%s", path);
  for (p = tmp; *p; p++) {
    if (*p == '/') {
      *p = '\0';
      if (mkdir(tmp, 0777)) {
        fprintf(stderr, "untape: mkdir '%s' failed\n", tmp);
        return 1;
      }
      *p = '/';
    }
  }
  return mkdir(tmp, 0777) != 0;
}

bool tape(const char** files, uint32_t nfile, const char* output) {
  FILE* fp = fopen(output, "wb");
  if (!fp) {
    return -1;
  }

  // write header
  tapeFile header = (tapeFile){
      .magic = TAPE_MAGIC,
      .nfile = nfile,
  };
  fwrite(&header, sizeof(tapeFile), 1, fp);

  // write files
  for (uint32_t i = 0; i < nfile; i++) {
    const char* name = files[i];
    char* data = NULL;
    size_t datalen = 0;
    if (__tape_readfile(name, &data, &datalen)) {
      fprintf(stderr, "tape: cannot read file %s\n", name);
      fclose(fp);
      return 1;
    }

    // write file
    tape_file file = (tape_file){
        .namelen = strlen(name) + 1,
        .size = datalen,
    };
    fwrite(&file, sizeof(tape_file), 1, fp);
    fwrite(name, 1, file.namelen, fp);
    fwrite(data, 1, datalen, fp);
    free(data);
  }
  fclose(fp);
  return 0;
}

bool untape(const char* input, char* outdir) {
  FILE* fp = fopen(input, "rb");
  if (!fp) {
    return 1;
  }
  tapeFile header;
  if (fread(&header, sizeof(tapeFile), 1, fp) != 1) {
    fclose(fp);
    return 1;
  }
  // check magic
  if (header.magic != TAPE_MAGIC) {
    fprintf(stderr, "Invalid untape file\n");
    fclose(fp);
    return 1;
  }

  // read files
  for (size_t i = 0; i < header.nfile; i++) {
    tape_file file;
    // read file info
    if (fread(&file, sizeof(tape_file), 1, fp) != 1) {
      fprintf(stderr, "Invalid untape file, read file failed\n");
      fclose(fp);
      return 1;
    }
    // read file name
    char* fname = malloc(file.namelen);
    if (fread(fname, file.namelen, 1, fp) != 1) {
      fprintf(stderr, "Invalid untape file, read file name failed\n");
      fclose(fp);
      free(fname);
      return 1;
    }
    // create dir
    if (__tape_directory(fname)) {
      fprintf(stderr, "Invalid untape file '%s' in '%s', create dir failed\n",
              fname, input);
      fclose(fp);
      free(fname);
      return 1;
    }
    FILE* ftmp = fopen(fname, "wb");
    if (ftmp == NULL) {
      fprintf(stderr, "Invalid untape file '%s' in '%s', create file failed\n",
              fname, input);
      fclose(fp);
      free(fname);
      return 1;
    }
    if (read_bytes_to_file(fp, ftmp, file.size)) {
      fprintf(stderr, "Invalid untape file '%s' in '%s', read file failed\n",
              fname, input);
      fclose(fp);
      fclose(ftmp);
      free(fname);
      return 1;
    }
    fclose(ftmp);
    free(fname);
  }

  return 0;
}

#endif